package com.gitee.wsl.ext.string

import com.gitee.wsl.ext.base.toHexStringUse0x
import com.gitee.wsl.lang.character.forEachCodePoint
import com.gitee.wsl.lang.character.highSurrogate
import com.gitee.wsl.lang.character.isBmpCodePoint
import com.gitee.wsl.lang.character.isValidCodePoint
import com.gitee.wsl.lang.character.lowSurrogate
import com.gitee.wsl.lang.character.toCodePoint
import kotlin.math.min


fun CharSequence.codePointCount(startIndex:Int,endIndex:Int):Int{
    var count = 0
    this.subSequence(startIndex,endIndex).forEachCodePoint { count++ }
    return count
}


/**
 * 统计字符串在 UTF-16 单元索引 [beginIndex, endIndex) 区间内的 Unicode code point 数量。
 */
fun CharSequence.codePointCountUtf16(beginIndex: Int = 0, endIndex: Int = length): Int {
    require(beginIndex in 0..length) { "beginIndex out of range" }
    require(endIndex in beginIndex..length) { "endIndex out of range" }

    var count = 0
    var i = beginIndex
    while (i < endIndex) {
        val ch = this[i]
        i += if (ch.isHighSurrogate() && i + 1 < endIndex && this[i + 1].isLowSurrogate()) {
            // 发现一个代理对，算作一个 code point
            2
        } else {
            1
        }
        count += 1
    }
    return count
}

fun CharSequence.codePointBefore(index: Int): Int {
    var index = index
    val c2 = this[--index]
    if (c2.isLowSurrogate() && index > 0) {
        val c1 = this[--index]
        if (c1.isHighSurrogate()) {
            return toCodePoint(c1, c2)
        }
    }
    return c2.code
}


fun CharSequence.codePointAt(index: Int): Int {
    var index = index
    val c1 = this[index]
    if (c1.isHighSurrogate() && ++index < length) {
        val c2 = this[index]
        if (c2.isLowSurrogate()) {
            return toCodePoint(c1, c2)
        }
    }
    return c1.code
}

/**
 * Returns the code points in this string.
 */
fun CharSequence.codePoints():List<Int> {
    val retList = mutableListOf<Int>()
    this.forEachCodePoint(retList::add)
    return retList
}

fun CharSequence.offsetByCodePoints(
    index: Int,
    codePointOffset: Int,
): Int {
    if (index !in 0..length) {
        throw IndexOutOfBoundsException()
    }

    var x = index
    if (codePointOffset >= 0) {
        var i: Int = 0
        while (x < length && i < codePointOffset) {
            if (this[x++].isHighSurrogate() && x < length &&
                this[x].isLowSurrogate()) {
                x++
            }
            i++
        }
        if (i < codePointOffset) {
            throw IndexOutOfBoundsException()
        }
    } else {
        var i: Int = codePointOffset
        while (x > 0 && i < 0) {
            if (this[--x].isLowSurrogate() && x > 0 &&
                this[x - 1].isHighSurrogate()) {
                x--
            }
            i++
        }
        if (i < 0) {
            throw IndexOutOfBoundsException()
        }
    }
    return x
}

/**
 * Appends the string representation of the [codePoint] argument to this sequence.
 *
 * See Java's `StringBuilder.appendCodePoint`.
 */
fun <T : Appendable> T.appendCodePoint(codePoint: Int): T {
    if (isBmpCodePoint(codePoint)) {
        append(codePoint.toChar())
    } else if (isValidCodePoint(codePoint)) {
        append(highSurrogate(codePoint))
        append(lowSurrogate(codePoint))
    } else {
        throw IllegalArgumentException("Not a valid Unicode code point: ${codePoint.toHexStringUse0x()}")
    }

    return this
}


/**
 * Returns the code points in this string.
 */
fun String.codePointIndices(): IntArray {
    val (array, size) = codePointIndicesFast(this)
    return array.copyOfRange(0, size)
}

/**
 * Returns the code points in [str], and the real size
 * of the returned array, since it is not trimmed to avoid
 * an additional potentially expensive allocation.
 */
internal fun codePointIndicesFast(str: String): Pair<IntArray, Int> {
    val strLength = str.length
    val intArray = IntArray(strLength + 1)
    var size = 0
    var i = 1

    intArray[size++] = 0

    while (i < strLength) {
        if (!hasSurrogatePairAtFast(str, i)) {
            intArray[size++] = i
        }

        i++
    }

    return Pair(intArray, min(size, strLength))
}


/**
 * Returns whether [str] has a unicode surrogate pair at the specified [index].
 */
private fun hasSurrogatePairAtFast(str: String, index: Int): Boolean {
    // Note(Edoardo): keep it like this for performance reasons.
    //  A const val is normally inlined, but it does not work in JS
    //  when that const val is inside a companion object, since it
    //  is lazily initialized.
    //  Without proper inlining you will have a null check (two calls deep)
    //  for every character in the string, losing significant time
    if (str[index - 1].code in /* MIN_HIGH_SURROGATE */ 0xD800..0xDBFF /* MAX_HIGH_SURROGATE */) {
        return str[index].code in /* MIN_LOW_SURROGATE */ 0xDC00..0xDFFF /* MAX_LOW_SURROGATE */
    }

    return false
}

fun Int.codePointToChars(): CharArray {
    return when (this) {
        in 0..0xFFFF -> {
            charArrayOf(toChar())
        }

        in 0x10000..0x10FFFF -> {
            val cpPrime = this - 0x10000
            val high = 0xD800 + (cpPrime shr 10)
            val low = 0xDC00 + (cpPrime and 0x3FF)
            charArrayOf(high.toChar(), low.toChar())
        }

        else -> {
            throw IllegalArgumentException("Invalid Unicode code point: $this")
        }
    }
}
